﻿using System;
using System.Collections.Generic;
using System.Text;
using PW.Mini.SlepowronskiJ.Siec.Budowa;
using Encog.ML.Data;
using Encog.ML.Data.Basic;
using Encog.ML.Train;
using Encog.ML.Train.Strategy;
using Encog.Neural.Networks.Training.Strategy;
using Encog.Neural.Networks.Training.Propagation.Back;
using Encog.Neural.Networks.Training.Propagation.Quick;
using Encog.Neural.Networks.Training.Propagation.Resilient;



namespace PW.Mini.SlepowronskiJ.Siec.Algorytmy
{
    [Serializable]
    public class EncogAlgorytmUczenia : AlgorytmUczenia
    {
        public static readonly string RPROP = "RPROP";
        public static readonly string QPROP = "QPROP";
        public static readonly string BACKPROPAGATION = "Backpropagation";

        [NonSerialized]
        private IMLTrain trainMain;

        private WzrostBleduWalidacyjnegoStrategia wzrostBleduWalidacyjnegoStrategia;

        public WzrostBleduWalidacyjnegoStrategia WzrostBleduWalidacyjnegoStrategia
        {
            get { return wzrostBleduWalidacyjnegoStrategia; }
        }

       #region konstruktor

        /// <summary>
        /// tworzy nowy obiekt klasy algorytmu uczacej z biblioteki Encog
        /// z parametrami wspolczynnikUczenia i momentu pobieranymi z Monitora
        /// </summary>
        public EncogAlgorytmUczenia()
            : base() 
        {
            this.nazwa = "MLP " + RPROP;
        }

        /// <summary>
        /// tworzy nowy obiekt klasy algorytmu uczacej z biblioteki Encog
        /// sieci neuronowej
        /// z parametrami wspolczynnikUczenia i momentu pobieranymi z Monitora
        /// </summary>
        /// <param name="siecNeuronowa">siec neuronowa do uczenia</param>
        public EncogAlgorytmUczenia(SiecNeuronowa siecNeuronowa)
            : base(siecNeuronowa)
        {
            this.nazwa = "MLP " + RPROP;
        }

        public override AlgorytmUczenia UtworzNowyAlgorytm()
        {
            return new EncogAlgorytmUczenia();
        }

        #endregion

        public String[] MetodyUczenia
        {
            get
            {
                return new String[] { 
                    "MLP "+BACKPROPAGATION, "MLP "+RPROP, //"MLP "+QPROP, 
                    "Elman "+BACKPROPAGATION, "Elman "+RPROP//, "Elman "+QPROP 
                };
            }
        }

        /// <summary>
        /// Metoda uczenia siec neuronowej z Walidacją na danych wejsciowych
        /// reprezentowanych przez macierz wartosci wraz z 
        /// oczekiwanymi wyjsciami dla tych danych 
        /// jesli wejscia lub wyjscia walidacyjne = null to bez walidacji
        /// </summary>
        /// <param name="wejscia">macierz wejsc testowych</param>
        /// <param name="wyjscia">macierz wyjsc oczekiwanych</param>
        /// <param name="wejsciaWalidacyjne">macierz wejsc walidacyjnych</param>
        /// <param name="wyjsciaWalidacyjne">macierz wyjsc walidacyjnych</param>
        public override void Uczenie(double[][] wejscia, double[][] wyjscia,
            double[][] wejsciaWalidacyjne, double[][] wyjsciaWalidacyjne)
        {
            base.Uczenie(wejscia, wyjscia, wejsciaWalidacyjne, wyjsciaWalidacyjne);
            IMLDataSet trainingSet = new BasicMLDataSet(wejscia, wyjscia);
            Train(trainingSet, wejsciaWalidacyjne, wyjsciaWalidacyjne);
        }

        private void Train(IMLDataSet trainingSet, double[][] wejsciaWalidacyjne, double[][] wyjsciaWalidacyjne)
        {
            this.monitor.NumerIteracji = 0;
            if (this.nazwa.Contains(RPROP))
                trainMain = new ResilientPropagation(this.siecNeuronowa, trainingSet);
            else if (this.nazwa.Contains(QPROP))
            {
                trainMain = new QuickPropagation(this.siecNeuronowa, trainingSet,
                    this.monitor.WspolczynnikUczenia);
                AdaptacyjnyWspolczynnikUczeniaStrategia awus = new AdaptacyjnyWspolczynnikUczeniaStrategia();
                trainMain.AddStrategy(awus);
            }
            else
            {
                //todo std BP encog
                //trainMain = new Backpropagation(this.siecNeuronowa, trainingSet,
                //    this.monitor.WspolczynnikUczenia, this.monitor.Moment);
                trainMain = new Backpropagation(this.siecNeuronowa, trainingSet);
                //AdaptacyjnyWspolczynnikUczeniaStrategia awus = new AdaptacyjnyWspolczynnikUczeniaStrategia();
                //awus.PreInit(this.monitor.WspolczynnikUczenia);
                //trainMain.AddStrategy(awus);
            }

            bool wyjsciaBipolarne = true;
            for (int i = 0; i < wyjsciaWalidacyjne.Length; ++i)
                for (int j = 0; j < wyjsciaWalidacyjne[i].Length; ++j)
                    if (wyjsciaWalidacyjne[i][j] != 1 && wyjsciaWalidacyjne[i][j] != -1)
                    {
                        wyjsciaBipolarne = false;
                        break;
                    }
            if(wyjsciaBipolarne)
                wzrostBleduWalidacyjnegoStrategia = new WzrostBleduWalidacyjnegoKlasyfikatorStrategia(3, false);
            else
                wzrostBleduWalidacyjnegoStrategia = new WzrostBleduWalidacyjnegoStrategia(3,false);
            wzrostBleduWalidacyjnegoStrategia.DaneWalidacyjne = new double[][][] { wejsciaWalidacyjne, wyjsciaWalidacyjne };
            StopTrainingStrategy stop2 = new StopTrainingStrategy(
                Math.Min(StopTrainingStrategy.DefaultMinImprovement,
                this.monitor.WarunekStopBleduSieci), 500);
            trainMain.AddStrategy(wzrostBleduWalidacyjnegoStrategia);
            trainMain.AddStrategy(stop2);

            do
            {
                trainMain.Iteration();
                this.monitor.BladSieci = trainMain.Error;
                //this.monitor.Logger.SendString("blad sieci " + this.monitor.BladSieci + " numer iteracji " + this.monitor.NumerIteracji);
                ++this.monitor.NumerIteracji;
                this.bledyIteracji.Add(this.monitor.BladSieci);
            }
            while (this.monitor.NumerIteracji < this.monitor.MaxLiczbaIteracji &&
                this.monitor.BladSieci > this.monitor.WarunekStopBleduSieci &&
                this.monitor.UczenieSieci && !stop2.ShouldStop()
                //todo wylaczylem wzrost walidacyjny
                //&& !wzrostBleduWalidacyjnegoStrategia.ShouldStop()
                );
            StringBuilder strBld = PobierzKomunikat();
            if (wzrostBleduWalidacyjnegoStrategia.ShouldStop())
            {
                //wzrostBleduWalidacyjnegoStrategia.PrzywrocNajlepszaSiec();
                strBld.AppendLine("uczenie przerwane z powodu trzykrotnego wzrostu błędu walidacyjnego");
            }
            if (stop2.ShouldStop())
                strBld.AppendLine("uczenie przerwane z powodu zbyt małych postępów uczenia (błąd malał o mniej niż " + Math.Min(StopTrainingStrategy.DefaultMinImprovement, this.monitor.WarunekStopBleduSieci) + " przez 500 iteracji)");
            this.komunikat = strBld.ToString();
            this.monitor.Logger.SendString(komunikat);
        }
    }
}
